package de.jmda.mview.typeshape.relation;

import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;

import de.jmda.mview.typeshape.RectangleSide;
import de.jmda.mview.typeshape.TypeShape;
import de.jmda.util.gui.awt.graphics.Compass;
import de.jmda.util.gui.awt.graphics.Compass.Direction;
import de.jmda.util.gui.awt.graphics.RelationEndpoint;
import de.jmda.util.gui.awt.graphics.RelationEndpoint.Orientation;
import de.jmda.util.gui.awt.graphics.RelationEndpoint.Style;

public class RelationEndpointHelper
{
	/**
	 * initial length of relation endpoint
	 */
	public final static int INITIAL_RELATION_ENDPOINT_LENGTH = 10;
	
	/**
	 * initial size of relation endpoint
	 */
	public final static int INITIAL_RELATION_ENDPOINT_SIZE = 4;

	/**
	 * injected into constructor
	 */
	private TypeShape<?> typeShape;

	/**
	 * injected into constructor
	 */
	private Style style;

	/**
	 * Defines distance of relation endpoint from type shape rectangle edge.
	 * <p>
	 * Modifiable via setter.
	 */
	private double anchorShiftingRatio = 0.5;

	/**
	 * Rectangle side where type shape relation is anchored.
	 * <p>
	 * Modifiable via setter.
	 */
	private RectangleSide anchorSide;
	
	/**
	 * Internal state, synchronized by {@link #synchronize()}.
	 */
	private RelationEndpoint relationEndpoint;

	public RelationEndpointHelper(
			TypeShape<?> myTypeShape, Style style, TypeShape<?> otherTypeShape)
  {
  	super();

  	this.typeShape = myTypeShape;
  	this.style = style;

  	Rectangle myRectangle = myTypeShape.getBounds();
  	Rectangle otherRectangle = otherTypeShape.getBounds();
  	
  	Point myCenterPoint =
  			new Point(
  					(int) myRectangle.getCenterX(),
  					(int) myRectangle.getCenterY());
  	Point otherCenterPoint =
				new Point(
						(int) otherRectangle.getCenterX(),
						(int) otherRectangle.getCenterY());

  	Direction direction = Compass.getDirection(myCenterPoint, otherCenterPoint);
		
		if (direction == Direction.NORTH)
		{
			// other is located north from source
			setAnchorSide(RectangleSide.TOP);
		}
		else if (direction == Direction.EAST)
		{
			// other is located east from source
			setAnchorSide(RectangleSide.RIGHT);
		}
		else if (direction == Direction.SOUTH)
		{
			// other is located south from source
			setAnchorSide(RectangleSide.BOTTOM);
		}
		else if (direction == Direction.WEST)
		{
			// other is located west from source
			setAnchorSide(RectangleSide.LEFT);
		}
		else if (myTypeShape == otherTypeShape)
		{
			// relation is from a type shape to itself
			setAnchorSide(RectangleSide.RIGHT);
		}
		else
		{
			throw new IllegalStateException("unsupported direction " + direction);
		}
  	
		synchronize();
  }
	
	public final static RectangleSide asRectangleSide(Orientation orientation)
	{
		if (orientation == Orientation.DOWN)
		{
			return RectangleSide.TOP;
		}
		if (orientation == Orientation.LEFT)
		{
			return RectangleSide.RIGHT;
		}
		if (orientation == Orientation.UP)
		{
			return RectangleSide.BOTTOM;
		}
		if (orientation == Orientation.RIGHT)
		{
			return RectangleSide.LEFT;
		}
		
		throw new IllegalArgumentException("unsupported orientation: " + orientation);
	}
	
	public RectangleSide getAnchorSide()
	{
		return anchorSide;
	}

	public Point getAnchorPoint()
	{
	  return relationEndpoint.getHeadLocation();
	}
	
	public double getAnchorShiftingRatio()
	{
		return anchorShiftingRatio;
	}

	public TypeShape<?> getTypeShape()
	{
		return typeShape;
	}
	
	public void setAnchorSide(RectangleSide anchorSide)
	{
		this.anchorSide = anchorSide;

		synchronize();
	}
	
	public void setAnchorShiftingRatio(double anchorShiftingRatio)
	{
		if ((anchorShiftingRatio >= 0) && (anchorShiftingRatio <= 1))
		{
			this.anchorShiftingRatio = anchorShiftingRatio;
		}

		synchronize();
	}
	
	public void setAnchor(RectangleSide anchorSide, double anchorShiftingRatio)
	{
		this.anchorSide = anchorSide;

		if ((anchorShiftingRatio >= 0) && (anchorShiftingRatio <= 1))
		{
			this.anchorShiftingRatio = anchorShiftingRatio;
		}

		synchronize();
	}

	public void setLength(int length)
	{
		relationEndpoint.setLength(length);

		synchronize();
	}
	
	/**
	 * Opposite of relation endpoint. Point that connects relation endpoint with
	 * relation to other relation endpoint.
	 * <p>
	 * Internal state, synchronized by {@link #synchronize()}.
	 */
	public Point getConnectorPoint()
	{
		// just a shortcut
		Point anchorPoint = getAnchorPoint();
		
		int relationEndpointLength = relationEndpoint.getLength();

		if (anchorSide == RectangleSide.TOP)
		{
  		return new Point(anchorPoint.x, anchorPoint.y - relationEndpointLength);
		}
  	else if (anchorSide == RectangleSide.BOTTOM)
		{
  		return new Point(anchorPoint.x, anchorPoint.y + relationEndpointLength);
		}
  	else if (anchorSide == RectangleSide.LEFT)
		{
  		return new Point(anchorPoint.x - relationEndpointLength, anchorPoint.y);
		}
  	else if (anchorSide == RectangleSide.RIGHT)
		{
  		return new Point(anchorPoint.x + relationEndpointLength, anchorPoint.y);
		}
  	else
  	{
  		throw new IllegalStateException("unsupported orientation " + anchorSide);
  	}
	}
	
	public RelationEndpoint getRelationEndpoint()
	{
		return relationEndpoint;
	}

	/**
	 * Call this method when {@link #relationEndpoint} state is changed.
	 */
  public void synchronize()
  {
		Rectangle rectangle = typeShape.getBounds();
		
		Point anchorPoint = new Point();
		
  	if (anchorSide == RectangleSide.TOP)
		{
  		anchorPoint.setLocation
			(
				rectangle.getMinX() + (rectangle.getWidth() * anchorShiftingRatio),
				rectangle.getMinY()
			);
		}
  	else if (anchorSide == RectangleSide.BOTTOM)
		{
  		anchorPoint.setLocation
			(
				rectangle.getMinX() + (rectangle.getWidth() * anchorShiftingRatio),
				rectangle.getMaxY()
  		);
		}
  	else if (anchorSide == RectangleSide.LEFT)
		{
  		anchorPoint.setLocation
			(
				rectangle.getMinX(),
				rectangle.getMinY() + (rectangle.getHeight() * anchorShiftingRatio)
			);
		}
  	else if (anchorSide == RectangleSide.RIGHT)
		{
  		anchorPoint.setLocation
			(
				rectangle.getMaxX(),
				rectangle.getMinY() + (rectangle.getHeight() * anchorShiftingRatio)
			);
		}
  	else
  	{
  		throw new IllegalStateException("unsupported orientation " + anchorSide);
  	}

  	if (relationEndpoint == null)
  	{
  		relationEndpoint =
		  		new RelationEndpoint(
		  				anchorPoint,
		  				INITIAL_RELATION_ENDPOINT_LENGTH,
		  				style,
		  				false,
		  				INITIAL_RELATION_ENDPOINT_SIZE,
		  				getOrientation());
  	}
  	else
  	{
  		relationEndpoint.setHeadLocation(anchorPoint);
  		relationEndpoint.setOrientation(getOrientation());
  	}
  }

  public Orientation getOrientation()
  {
  	if (anchorSide == RectangleSide.TOP)
  	{
  		return Orientation.DOWN;
  	}
  	if (anchorSide == RectangleSide.BOTTOM)
  	{
  		return Orientation.UP;
  	}
  	if (anchorSide == RectangleSide.LEFT)
  	{
  		return Orientation.RIGHT;
  	}
		return Orientation.LEFT;
  }
  
  public boolean isHorizontal()
  {
  	return getOrientation().isHorizontal();
  }
  
  public boolean isVertical()
  {
  	return getOrientation().isVertical();
  }

	/**
	 * <pre>
	 * <-o o->
	 * <p>
	 * ^
	 * |
	 * o
	 * 
	 * o
	 * |
	 * v
	 * </pre>
	 *
	 * @param other
	 * @return
	 */
	public boolean connectorPointsFacing(RelationEndpointHelper other)
  {
		if (isHorizontal())
		{
			if (false == other.isHorizontal())
			{
				return false;
			}

			if (getConnectorPoint().x <= other.getConnectorPoint().x)
			{
				return ((      getOrientation() == Orientation.LEFT) &&
				        (other.getOrientation() == Orientation.RIGHT));
			}

			return ((      getOrientation() == Orientation.RIGHT) &&
	            (other.getOrientation() == Orientation.LEFT));
		}
		else
		{
			if (false == other.isVertical())
			{
				return false;
			}

			if (getConnectorPoint().y <= other.getConnectorPoint().y)
			{
				return ((      getOrientation() == Orientation.UP) &&
				        (other.getOrientation() == Orientation.DOWN));
			}

			return ((      getOrientation() == Orientation.DOWN) &&
	            (other.getOrientation() == Orientation.UP));
		}
  }
	
	public void paint(Graphics2D g)
	{
		relationEndpoint.paint(g);
	}
}